With the Framer Motion library, we can render animations in our React app easily.
In this article, we’ll take a look at how to get started with Framer Motion.
Shared Layout Animations
We can create shared layout animations with the AnimateSharedLayout component.
For example, we can write:
App.js
import React, { useState } from "react";
import { AnimatePresence, AnimateSharedLayout, motion } from "framer-motion";
import "./styles.css";
function Item({ text }) {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => setIsOpen(!isOpen);
return (
<motion.li layout onClick={toggleOpen} initial={{ borderRadius: 10 }}>
<motion.div layout>{text}</motion.div>
<AnimatePresence>{isOpen && <Content />}</AnimatePresence>
</motion.li>
);
}
function Content() {
return (
<motion.div
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div className="row" />
<div className="row" />
<div className="row" />
</motion.div>
);
}
const items = [
{ text: "foo", id: 1 },
{ text: "bar", id: 2 },
{ text: "baz", id: 3 }
];
export default function App() {
return (
<AnimateSharedLayout>
<motion.ul layout>
{items.map(({ text, id }) => (
<Item text={text} key={id} />
))}
</motion.ul>
</AnimateSharedLayout>
);
}
styles.css
html,
body {
min-height: 100vh;
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
background-repeat: no-repeat;
display: flex;
justify-content: center;
align-items: center;
}
.App {
font-family: sans-serif;
text-align: center;
}
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
ul {
width: 300px;
display: flex;
flex-direction: column;
background: white;
padding: 20px;
border-radius: 25px;
}
li {
background-color: rgba(214, 214, 214, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
overflow: hidden;
cursor: pointer;
}
li:last-child {
margin-bottom: 0px;
}
.avatar {
width: 40px;
height: 40px;
background-color: #666;
border-radius: 20px;
}
.row {
width: 100%;
height: 8px;
background-color: #999;
border-radius: 10px;
margin-top: 12px;
}
We have the Item component that lets us toggle the content of the div.
This is done with the AnimatPresence component which shows the Content component’s content if it’s true .
The row class creates the bars that we want to show.
In App , we have the AnimateSharedLayout component wrapped around our ul to let us show the same animation for each li .
The animation is synced across a set of components that don’t share state.
And they let us perform animation between different components with a common layoutId as they’re added or removed.
When a component with the 1ayoutId prop is removed from one part of the tree and it’s added elsewhere, the new component will automatically animate from the old component’s position.
For example, we can write:
App.js
import React, { useState } from "react";
import { AnimateSharedLayout, motion } from "framer-motion";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState(colors[0]);
return (
<AnimateSharedLayout>
<ul>
{colors.map((color) => (
<Item
key={color}
color={color}
isSelected={selected === color}
onClick={() => setSelected(color)}
/>
))}
</ul>
</AnimateSharedLayout>
);
}
function Item({ color, isSelected, onClick }) {
return (
<li className="item" onClick={onClick} style={{ backgroundColor: color }}>
{isSelected && (
<motion.div
layoutId="outline"
className="outline"
initial={false}
animate={{ borderColor: color }}
transition={spring}
/>
)}
</li>
);
}
const colors = ["red", "green", "blue", "yellow"];
const spring = {
type: "spring",
stiffness: 500,
damping: 30
};
styles.css
html,
body {
min-height: 100vh;
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
background-repeat: no-repeat;
display: flex;
justify-content: center;
align-items: center;
}
.App {
font-family: sans-serif;
text-align: center;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
width: 280px;
height: 380px;
}
.item {
width: 100px;
height: 50px;
margin: 20px;
position: relative;
cursor: pointer;
flex-shrink: 0;
}
.outline {
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
border: 10px solid white;
}
We add a frame around the li that’s clicked on by toggling the isSelected state in the Item component.
In styles.css , we have the outline class to define the outline.
That is toggled when we toggle the li .
In the App component, we select the item that we clicked on with the setSelected function.
Conclusion
We can add shared layouts animation to perform animations synced across a set of components that don’t share states.
And we can also perform animations between different components with a common layoutId as they’re added or removed.